<?php
/**
 * Created by PhpStorm.
 * User: 程阳
 * Date: 2018/3/20
 * Time: 22:04
 */

namespace app\api\service;


use app\api\model\User;
use app\lib\enum\ScopeEnum;
use app\lib\exception\TokenException;
use app\lib\exception\WeChatException;
use think\Exception;

class UserToken extends Token
{
    protected $code;
    protected $wxLoginUrl;
    protected $wxAppID;
    protected $wxAppSecret;

    public function __construct($code) {
        $this->code = $code;
        $this->wxAppID = config('wx.app_id');
        $this->wxAppSecret = config('wx.app_secret');
        $this->wxLoginUrl = sprintf(config('wx.login_url'),$this->wxAppID,$this->wxAppSecret,$this->code);
    }

    /**
     * 根据code获得openid
     * @return string
     * @throws Exception
     * @throws WeChatException
     */
    public function get(){
        $result = curl_get($this->wxLoginUrl);

        // 第二个参数为true，使字符串被转化为数组而非对象
        $wxResult = json_decode($result,true);
        if(empty($wxResult)){
            // 为什么以empty判断是否错误，这是根据微信返回规则摸索出来的，这种情况通常是由于传入不合法的code
            throw new Exception('获取session_key及openID时异常，微信内部错误');
        }else{
            // 建议用明确的变量来表示是否成功
            // 微信服务器并不会将错误标记为400，无论成功还是失败都标记为200
            // 这样非常不好判断，只能用errorCode是否存在来判断
            $loginFail = array_key_exists('errcode',$wxResult);
            if($loginFail){
                throw new WeChatException([
                    'msg' => $wxResult['errmsg'],
                    'errorCode' => $wxResult['errcode']
                ]);
            }else{
                return $this->grantToken($wxResult);
            }
        }
    }

    /**
     * 生成用户token
     * @param $wxResult
     * @return string
     */
    private function grantToken($wxResult){
        $openid = $wxResult['openid'];
        $user = User::getByOpenID($openid);
        if(!$user){
            // 用户不存在，创建新用户
            $uid = $this->newUser($openid);
        }else{
            $uid = $user->id;
        }
        // 准备写入缓存的数据
        $cachedValue = $this->prepareCachedValue($wxResult,$uid);
        // 将数据写入缓存
        $token = $this->saveToCache($cachedValue);
        return $token;
    }

    /**
     * 准备写入缓存的数据
     * @param $wxResult
     * @param $uid
     * @return mixed
     */
    private function prepareCachedValue($wxResult,$uid){
        $cacheValue = $wxResult;
        $cacheValue['uid'] = $uid;
        $cacheValue['scope'] = ScopeEnum::User;
        return $cacheValue;
    }

    /**
     * 写入缓存，并返回token
     * @param $cachedValue
     * @return string
     * @throws TokenException
     */
    private function saveToCache($cachedValue){
        // 缓存的键
        $key = self::generateToken();
        // 缓存的值
        $value = json_encode($cachedValue);
        // 缓存失效时间
        $expire_in = config('setting.token_expire_in');
        // 写入缓存
        $result = cache($key,$value,$expire_in);

        if(!$result){
            throw new TokenException([
                'msg' => '服务器缓存异常',
                'errorCode' => 10005
            ]);
        }
        // 返回token
        return $key;
    }

    /**
     * 创建新用户
     * @param $openid
     * @return $this
     */
    private function newUser($openid){
        $user = User::create(['openid' => $openid]);
        return $user;
    }
}